//===--- ParseableOutput.cpp - Helpers for parseable output ---------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

#include "polarphp/driver/ParseableOutput.h"

#include "polarphp/basic/FileTypes.h"
#include "polarphp/basic/JSONSerialization.h"
#include "polarphp/basic/TaskQueue.h"
#include "polarphp/driver/Action.h"
#include "polarphp/driver/Job.h"
#include "llvm/Option/Arg.h"
#include "llvm/Support/raw_ostream.h"

using namespace polar::driver::parseable_output;
using namespace polar::driver;
using namespace polar::sys;
using namespace polar;

namespace {
struct CommandInput {
   std::string Path;
   CommandInput() {}
   CommandInput(StringRef Path) : Path(Path) {}
};

using OutputPair = std::pair<filetypes::FileTypeId, std::string>;
} // end anonymous namespace

namespace polar {
namespace json {
template<>
struct ScalarTraits<CommandInput> {
   static void output(const CommandInput &value, llvm::raw_ostream &os) {
      os << value.Path;
   }
   static bool mustQuote(StringRef) { return true; }
};

template <> struct ScalarEnumerationTraits<filetypes::FileTypeId> {
   static void enumeration(Output &out, filetypes::FileTypeId &value) {
      filetypes::for_all_types([&](filetypes::FileTypeId ty) {
         std::string typeName = filetypes::get_type_name(ty);
         out.enumCase(value, typeName.c_str(), ty);
      });
   }
};

template <> struct ObjectTraits<std::pair<filetypes::FileTypeId, std::string>> {
   static void mapping(Output &out,
                       std::pair<filetypes::FileTypeId, std::string> &value) {
      out.mapRequired("type", value.first);
      out.mapRequired("path", value.second);
   }
};

template<typename T, unsigned N>
struct ArrayTraits<SmallVector<T, N>> {
   static size_t size(Output &out, SmallVector<T, N> &seq) {
      return seq.size();
   }

   static T &element(Output &out, SmallVector<T, N> &seq, size_t index) {
      if (index >= seq.size())
         seq.resize(index+1);
      return seq[index];
   }
};
} // namespace json
} // namespace swift

namespace {

class Message {
   std::string Kind;
   std::string Name;
public:
   Message(StringRef Kind, StringRef Name) : Kind(Kind), Name(Name) {}
   virtual ~Message() = default;

   virtual void provideMapping(polar::json::Output &out) {
      out.mapRequired("kind", Kind);
      out.mapRequired("name", Name);
   }
};

class CommandBasedMessage : public Message {
public:
   CommandBasedMessage(StringRef Kind, const Job &Cmd) :
      Message(Kind, Cmd.getSource().getClassName()) {}
};

class DetailedCommandBasedMessage : public CommandBasedMessage {
   std::string Executable;
   SmallVector<std::string, 16> Arguments;
   std::string CommandLine;
   SmallVector<CommandInput, 4> Inputs;
   SmallVector<OutputPair, 8> Outputs;
public:
   DetailedCommandBasedMessage(StringRef Kind, const Job &Cmd) :
      CommandBasedMessage(Kind, Cmd) {
      Executable = Cmd.getExecutable();
      for (const auto &A : Cmd.getArguments()) {
         Arguments.push_back(A);
      }
      llvm::raw_string_ostream wrapper(CommandLine);
      Cmd.printCommandLine(wrapper, "");
      wrapper.flush();

      for (const Action *A : Cmd.getSource().getInputs()) {
         if (const auto *IA = dyn_cast<InputAction>(A))
            Inputs.push_back(CommandInput(IA->getInputArg().getValue()));
      }

      for (const Job *J : Cmd.getInputs()) {
         auto OutFiles = J->getOutput().getPrimaryOutputFilenames();
         if (const auto *BJAction = dyn_cast<BackendJobAction>(&Cmd.getSource())) {
            Inputs.push_back(CommandInput(OutFiles[BJAction->getInputIndex()]));
         } else {
            for (const std::string &FileName : OutFiles) {
               Inputs.push_back(CommandInput(FileName));
            }
         }
      }

      // TODO: set up Outputs appropriately.
      filetypes::FileTypeId PrimaryOutputType = Cmd.getOutput().getPrimaryOutputType();
      if (PrimaryOutputType != filetypes::TY_Nothing) {
         for (const std::string &OutputFileName : Cmd.getOutput().
            getPrimaryOutputFilenames()) {
            Outputs.push_back(OutputPair(PrimaryOutputType, OutputFileName));
         }
      }
      filetypes::for_all_types([&](filetypes::FileTypeId Ty) {
         for (auto Output : Cmd.getOutput().getAdditionalOutputsForType(Ty)) {
            Outputs.push_back(OutputPair(Ty, Output));
         }
      });
   }

   void provideMapping(polar::json::Output &out) override {
      Message::provideMapping(out);
      out.mapRequired("command", CommandLine); // Deprecated, do not document
      out.mapRequired("command_executable", Executable);
      out.mapRequired("command_arguments", Arguments);
      out.mapOptional("inputs", Inputs);
      out.mapOptional("outputs", Outputs);
   }
};

class TaskBasedMessage : public CommandBasedMessage {
   int64_t Pid;
public:
   TaskBasedMessage(StringRef Kind, const Job &Cmd, int64_t Pid) :
      CommandBasedMessage(Kind, Cmd), Pid(Pid) {}

   void provideMapping(polar::json::Output &out) override {
      CommandBasedMessage::provideMapping(out);
      out.mapRequired("pid", Pid);
   }
};

class BeganMessage : public DetailedCommandBasedMessage {
   int64_t Pid;
   TaskProcessInformation ProcInfo;

public:
   BeganMessage(const Job &Cmd, int64_t Pid, TaskProcessInformation ProcInfo)
      : DetailedCommandBasedMessage("began", Cmd), Pid(Pid),
        ProcInfo(ProcInfo) {}

   void provideMapping(polar::json::Output &out) override {
      DetailedCommandBasedMessage::provideMapping(out);
      out.mapRequired("pid", Pid);
      out.mapRequired("process", ProcInfo);
   }
};

class TaskOutputMessage : public TaskBasedMessage {
   std::string Output;
   TaskProcessInformation ProcInfo;

public:
   TaskOutputMessage(StringRef Kind, const Job &Cmd, int64_t Pid,
                     StringRef Output, TaskProcessInformation ProcInfo)
      : TaskBasedMessage(Kind, Cmd, Pid), Output(Output), ProcInfo(ProcInfo) {}

   void provideMapping(polar::json::Output &out) override {
      TaskBasedMessage::provideMapping(out);
      out.mapOptional("output", Output, std::string());
      out.mapRequired("process", ProcInfo);
   }
};

class FinishedMessage : public TaskOutputMessage {
   int ExitStatus;
public:
   FinishedMessage(const Job &Cmd, int64_t Pid, StringRef Output,
                   TaskProcessInformation ProcInfo, int ExitStatus)
      : TaskOutputMessage("finished", Cmd, Pid, Output, ProcInfo),
        ExitStatus(ExitStatus) {}

   void provideMapping(polar::json::Output &out) override {
      TaskOutputMessage::provideMapping(out);
      out.mapRequired("exit-status", ExitStatus);
   }
};

class SignalledMessage : public TaskOutputMessage {
   std::string ErrorMsg;
   Optional<int> Signal;
public:
   SignalledMessage(const Job &Cmd, int64_t Pid, StringRef Output,
                    StringRef ErrorMsg, Optional<int> Signal,
                    TaskProcessInformation ProcInfo)
      : TaskOutputMessage("signalled", Cmd, Pid, Output, ProcInfo),
        ErrorMsg(ErrorMsg), Signal(Signal) {}

   void provideMapping(polar::json::Output &out) override {
      TaskOutputMessage::provideMapping(out);
      out.mapOptional("error-message", ErrorMsg, std::string());
      out.mapOptional("signal", Signal);
   }
};

class SkippedMessage : public DetailedCommandBasedMessage {
public:
   SkippedMessage(const Job &Cmd) :
      DetailedCommandBasedMessage("skipped", Cmd) {}
};

} // end anonymous namespace

namespace polar {
namespace json {

template<>
struct ObjectTraits<Message> {
   static void mapping(Output &out, Message &msg) {
      msg.provideMapping(out);
   }
};

} // namespace json
} // namespace polar

static void emitMessage(raw_ostream &os, Message &msg) {
   std::string JSONString;
   llvm::raw_string_ostream BufferStream(JSONString);
   json::Output yout(BufferStream);
   yout << msg;
   BufferStream.flush();
   os << JSONString.length() << '\n';
   os << JSONString << '\n';
}

void parseable_output::emitBeganMessage(raw_ostream &os, const Job &Cmd,
                                        int64_t Pid,
                                        TaskProcessInformation ProcInfo) {
   BeganMessage msg(Cmd, Pid, ProcInfo);
   emitMessage(os, msg);
}

void parseable_output::emitFinishedMessage(raw_ostream &os, const Job &Cmd,
                                           int64_t Pid, int ExitStatus,
                                           StringRef Output,
                                           TaskProcessInformation ProcInfo) {
   FinishedMessage msg(Cmd, Pid, Output, ProcInfo, ExitStatus);
   emitMessage(os, msg);
}

void parseable_output::emitSignalledMessage(raw_ostream &os, const Job &Cmd,
                                            int64_t Pid, StringRef ErrorMsg,
                                            StringRef Output,
                                            Optional<int> Signal,
                                            TaskProcessInformation ProcInfo) {
   SignalledMessage msg(Cmd, Pid, Output, ErrorMsg, Signal, ProcInfo);
   emitMessage(os, msg);
}

void parseable_output::emitSkippedMessage(raw_ostream &os, const Job &Cmd) {
   SkippedMessage msg(Cmd);
   emitMessage(os, msg);
}
